package references

import (
	"hi-jvm/instructions/base"
	"hi-jvm/rtda"
	"hi-jvm/rtda/heap"
)

// 调用成员函数
// 对超类、私有和实例初始化方法调用的特殊处理
type INVOKE_SPECIAL struct{ base.Index16Instruction }

// 注意：
// methodRefw为被当前函数调用的函数
func (self *INVOKE_SPECIAL) Execute(frame *rtda.Frame) {
	methodRef := getMethodRef(frame, self.Index)
	checkMethodRef_special(methodRef)
	checkNullInvokeAndProtectedMethod(frame, methodRef.ResolvedMethod())

	methodToBeInvoked := getMethodToBeInvoked(frame, methodRef)

	base.InvokeMethod(frame, methodToBeInvoked)
}

func checkMethodRef_special(methodRef *heap.MethodRef) {
	resolvedMethod := methodRef.ResolvedMethod()
	if resolvedMethod.Name == "<init>" && resolvedMethod.Class != methodRef.ResolvedClass() {
		panic("java.lang.NoSuchMethodError")
	}
	if resolvedMethod.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
}

func getMethodRef(frame *rtda.Frame, index uint) (methodRef *heap.MethodRef) {
	currentClass := frame.Method.Class
	cp := currentClass.ConstantPool
	methodRef = cp.GetConstant(index).(*heap.MethodRef)
	return
}


// 确保非空对象调用
// 确保protected只能被类本身或子类调用()
func checkNullInvokeAndProtectedMethod(frame *rtda.Frame,  resolvedMethod *heap.Method) {
	ref := frame.OperandStack.GetRefFromTop(resolvedMethod.ArgSlotCount - 1)
	if ref == nil {
		panic("java.lang.NullPointerException")
	}

	currentClass := frame.Method.Class
	if !resolvedMethod.IsProtected() {
		return
	}

	if resolvedMethod.Class.IsSuperClassOf(currentClass) &&	// 是当前类的父类
		resolvedMethod.Class.GetPackageName() != currentClass.GetPackageName() &&	// 在同一个包中
		ref.Class != currentClass &&	// 不是同一个类
		!ref.Class.IsSubClassOf(currentClass) {	// 不是子类

		panic("java.lang.IllegalAccessError")
	}
}

func getMethodToBeInvoked(frame *rtda.Frame, methodRef *heap.MethodRef) *heap.Method {
	resolvedMethod := methodRef.ResolvedMethod()
	methodToBeInvoked := resolvedMethod
	currentClass := frame.Method.Class
	resolvedClass := methodRef.ResolvedClass()

	// 如果是父类的函数， 且不是构造函数
	if currentClass.IsSuper() &&
		resolvedClass.IsSuperClassOf(currentClass) &&
		resolvedMethod.Name != "<init>" {
		methodToBeInvoked = heap.LookupMethodInClass(currentClass.SuperClass,
			methodRef.Name, methodRef.Descriptor)
	}

	if methodToBeInvoked == nil || methodToBeInvoked.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	return methodToBeInvoked
}

